﻿#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <limits.h>
//int main()
//{
//	//int a = INT_MAX;   // 2147483647  整型的最大值
//	int a = 2147483646;
//	int b = 2147483646;
//	int avg = (a + b) / 2;  // 结果-2 因为超过最大值了 我们换一种方式，先求二者差除2 最让加上被减的  b+（a-b）/2
//	int avg2 = a + (b - a) / 2;
//	printf("%d", avg2);  
//
//	return 0;
//}

//数学中我们其实就⻅过函数的概念，⽐如：⼀次函数 y = kx + b ，k和b都是常数，给⼀个任意的x，就
//得到⼀个y值。
//其实在C语⾔也引⼊函数（function）的概念，有些翻译为：⼦程序，⼦程序这种翻译更加准确⼀些。
//C语⾔中的函数就是⼀个完成某项特定的任务的⼀⼩段代码。这段代码是有特殊的写法和调⽤⽅法的。
//C语⾔的程序其实是由⽆数个⼩的函数组合⽽成的，也可以说：⼀个⼤的计算任务可以分解成若⼲个较
//⼩的函数（对应较⼩的任务）完成。同时⼀个函数如果能完成某项特定任务的话，这个函数也是可以
//复⽤的，提升了开发软件的效率。
//在C语⾔中我们⼀般会⻅到两类函数：
//• 库函数
//• ⾃定义函数

//C语⾔标准中规定了C语⾔的各种语法规则，C语⾔并不提供库函数；C语⾔的国际标准ANSI C规定了⼀
//些常⽤的函数的标准，被称为标准库，那不同的编译器⼚商根据ANSI提供的C语⾔标准就给出了⼀系列
//函数的实现。这些函数就被称为库函数。
//我们前⾯内容中学到的 printf 、 scanf 都是库函数，库函数的也是函数，不过这些函数已经是现
//成的，我们只要学会就能直接使⽤了。有了库函数，⼀些常⻅的功能就不需要程序员⾃⼰实现了，⼀
//定程度提升了效率；同时库函数的质量和执⾏效率上都更有保证。
//各种编译器的标准库中提供了⼀系列的库函数，这些库函数根据功能的划分，都在不同的头⽂件中进
//⾏了声明。
//库函数相关头⽂件：https ://zh.cppreference.com/w/c/header
//有数学相关的，有字符串相关的，有⽇期相关的等，每⼀个头⽂件中都包含了，相关的函数和类型等
//信息，库函数的学习不⽤着急⼀次性全部学会，慢慢学习，各个击破就⾏。

//库函数是在标准库中对应的头⽂件中声明的，所以库函数的使⽤，务必包含对应的头⽂件，不包含是
//可能会出现⼀些问题的。

#include <math.h>
//int main()
//{
//	double r = sqrt(16);  // 返回平方根
//	printf("%lf", r);   // double打印要lf
//	return 0;
//}


//自定义函数
//ret_type fun_name(形式参数)
//{
//}
//• ret_type 是函数返回类型
//• fun_name 是函数名  尽量选择和函数功能相关的名字
//• 括号中放的是形式参数  可以有多个参数
//•{}括起来的是函数体

//写⼀个加法函数，完成2个整型变量的加法操作。

//int Add(int x, int y)  // x y是形参  形式参数  
//{
//	return (x + y);  // 直接返回这两个参数的和
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	// 输入
//	scanf("%d %d", &a, &b);
//	// 调用函数
//	int r = Add(a,b);  // 往函数里面传两个参数a b   a b是实参 实际参数
//	printf("%d", r);
//	return 0;
//}

//函数的参数部分需要交代清楚：参数个数，每个参数的类型是啥，形参的名字叫啥。

//为什么叫形式参数呢？实际上，如果只是定义了 Add 函数，⽽不去调⽤的话， Add 函数的参数 x
//和 y 只是形式上存在的，不会向内存申请空间，不会真实存在的，所以叫形式参数。形式参数只有在
//函数被调⽤的过程中为了存放实参传递过来的值，才向内存申请空间，这个过程就是形式的实例化。

//void print()  // 无参 无返回值    如果明确表示不需要参数只需要加上void即可,写了下面如果传参就不能运行了，报警告
//{
//	printf("hello world\n");
//}
//
//int main()
//{
//	print();
//	print(1);  // 虽然没写参数，但是还是可以传参，只是我不用所传的参数
//	return 0;
//}

//传递给函数的参数a和b，称为实际参数，简称实参。
//实际参数就是真实传递给函数的参数。

//int Add(int x, int y)  // x y是形参  形式参数  
//{
//	return (x + y);  // 直接返回这两个参数的和
//}
//int main()
//{
//	int a = 0;
//	int b = 0;
//	// 输入
//	scanf("%d %d", &a, &b);
//	// 调用函数
//	int r = Add(a,b);  // 往函数里面传两个参数a b   a b是实参 实际参数
//	printf("%d", r);
//	return 0;
//}


//我们在调试的可以观察到，x和y确实得到了a和b的值，但是x和y的地址和a和b的地址是不⼀样的，所
//以我们可以理解为形参是实参的⼀份临时拷⻉。

//在函数的设计中，函数中经常会出现return语句，这⾥讲⼀下return语句使⽤的注意事项。
//• return后边可以是⼀个数值，也可以是⼀个表达式，如果是表达式则先执⾏表达式，再返回表达式
//的结果。
//• return后边也可以什么都没有，直接写 return; 这种写法适合函数返回类型是void的情况。
//• return返回的值和函数返回类型不⼀致，系统会⾃动将返回的值隐式转换为函数的返回类型。
//• return语句执⾏后，函数就彻底返回，后边的代码不再执⾏。
//• 如果函数中存在if等分⽀的语句，则要保证每种情况下都有return返回，否则会出现编译错误。

//int test()
//{
//	int a = 0;
//	scanf("%d", &a);
//	if (a > 0)
//		return 1;
//	else
//		return -1;
//}
//int main()
//{
//	int ret = test();
//	printf("%d\n", ret);
//	return 0;
//}

//void test()
//{
//	printf("hehe\n");
//	if (1)
//		return;  // 提前返回，不再执行下面的语句
//		printf("hahah\n");
//
//}
//int main()
//{
//	test();
//	return 0;
//}

//int test()
//{
//	return 3.14;  // 这是double类型的
//}
//
//int main()
//{
//	int r = test();
//	printf("%d", r);  // 3   编译器有警告 从“double”转换到“int”，可能丢失数据
//	return 0;
//}


// 函数如果不写返回值，默认返回整型
//int test()
//{
//	int a = 0;
//	scanf("%d", &a);   // 编译器报警“test”: 不是所有的控件路径都返回值  所以我们要保证每种分支路径都有返回值
//	if (a)
//		return 1;
//}
//int main()
//{
//	int r = test();
//	return 0;
//}

// 函数的返回类型如果不写，编译器会认为返回int类型的值
// 如果函数要求返回值，但是函数中没有return语句 ，返回值具体是什么不确定
//test()
//{
//	printf("hehe\n");
//}
//
//int main()
//{
//	int r = test();
//	printf("%d", r); // 返回5 猜测是因为打印了五个字符
//	return 0;
//}

//写⼀个函数对将⼀个整型数组的内容，全部置为 - 1，再写⼀个函数打印数组的内容。
//void set_arr(int arr[10],int sz,int set)  // 10可写可不写 因为这两个数组是同一个数组，不会创建新的数组
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		arr[i] = set;
//	}
//}
//
//print_arr(int arr[10],int sz)
//{
//	int i = 0;
//	for (i = 0; i < sz; i++)
//	{
//		printf("%d ", arr[i]);
//	}
//	printf("\n");  // 保证每次调用这个函数打印的内容都在不同行
//}
//int main()
//{
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	// 写一个函数将数组的内容全部设置为-1  数组传参传数组名和元素个数
//	int sz = sizeof(arr) / sizeof(arr[0]); // 计算元素个数
//	print_arr(arr, sz); // 没改变之前打印
//	set_arr(arr,sz,-1);  // 不要写arr[10] 是写数组名   多加一个参数这样我想初始化什么就初始化什么
//	// 不会局限与-1
//	print_arr(arr, sz);
//}

//这⾥我们需要知道数组传参的⼏个重点知识：
//• 函数的形式参数要和函数的实参个数匹配
//• 函数的实参是数组，形参也是可以写成数组形式的
// 实参和形参的名字可以一样，也可以不一样都可以的
// 函数在设计的时候一定要功能单一，这样可以复用
//• 形参如果是⼀维数组，数组⼤⼩可以省略不写
//• 形参如果是⼆维数组，⾏可以省略，但是列不能省略
//• 数组传参，形参是不会创建新的数组的
//• 形参操作的数组和实参的数组是同⼀个数组 

//print(int arr[3][5], int r, int c)
//{
//	int i = 0;
//	for (i = 0; i < r; i++)
//	{
//		int j = 0;
//		for (j = 0; j < c; j++)
//		{
//			printf("%d ", arr[i][j]);
//		}
//		printf("\n");  // 打印完一行换行
//	}
//}
//
//int main()
//{
//	int arr[3][5] = { 1,2,3,4,5 ,2,3,4,5,6 ,3,4,5,6,7 };
//	// 打印二维数组的内容
//	print(arr,3,5);
//}

//嵌套调⽤就是函数之间的互相调⽤，每个函数就⾏⼀个乐⾼零件，正是因为多个乐⾼的零件互相⽆缝
//的配合才能搭建出精美的乐⾼玩具，也正是因为函数之间有效的互相调⽤，最后写出来了相对⼤型的
//程序。
//假设我们计算某年某⽉有多少天？如果要函数实现，可以设计2个函数 :
//• is_leap_year()：根据年份确定是否是闰年
//• get_days_of_month()：调⽤is_leap_year确定是否是闰年后，再根据⽉计算这个⽉的天数

// 计算某年某月有几天  
// 31 28 31 30 31 30 31 31 30 31 30 31 平年  只有闰年的二月有29天

//int is_leap_year(int y)
//{
//	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
//		return 1;  // 是闰年就返回1
//	else
//		return 0;
//}
//int get_days_of_month(int y, int m)
//{
//	int days[] = { 0 ,31,28,31,30,31,30,31,31,30,31,30,31 };  // 多加一个0 让下标和月份对上
//
//	int day = days[m];
//	// 分装一个函数来判断闰年
//	if (is_leap_year(y) == 1&&m == 2)
//	{
//		day++;
//	}
//	return day;
//}
//
//int main()
//{
//	int year = 0;
//	int month = 0;
//	scanf("%d %d", &year, &month);
//	// 计算某年某月有几天
//	int day = get_days_of_month(year, month);
//	printf("%d\n", day);
//	return 0;
//}


// 是闰年 - 真  不是闰年 - 假 所以我们可以使用布尔类型
//int is_leap_year(int y)
//{
//	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
//		return 1;  // 是闰年就返回1
//	else
//		return 0;
//}
#include <stdbool.h>
//bool is_leap_year(int y)
//{
//	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
//		return true;  // 是闰年就返回true
//	else
//		return false;
//}
//int get_days_of_month(int y, int m)
//{
//	int days[] = { 0 ,31,28,31,30,31,30,31,31,30,31,30,31 };  // 多加一个0 让下标和月份对上
//
//	int day = days[m];
//	// 分装一个函数来判断闰年
//	if (is_leap_year(y) && m == 2)  // ==1可以不写  因为1为真 0为假
//	{
//		day++;
//	}
//	return day;
//}
//
//int main()
//{
//	int year = 0;
//	int month = 0;
//	scanf("%d %d", &year, &month);
//	// 计算某年某月有几天
//	int day = get_days_of_month(year, month);
//	printf("%d\n", day);
//	return 0;
//}

//所谓链式访问就是将⼀个函数的返回值作为另外⼀个函数的参数，像链条⼀样将函数串起来就是函数
//的链式访问。

#include <string.h>
//int main()
//{
//	//int len = strlen("abcdef");
//	//printf("%d\n", len);
//
//	printf("%d\n", strlen("abcdef"));  // 链式访问
//	return 0;
//}

// printf的返回值是 int类型的 打印的字符数  打印了2个字符 返回值是2 
//int main()
//{
//	printf("%d\n", printf("%d", printf("%d", 43))); // 结果4321
//	// 最里面的printf 打印43 所以返回值是2
//	// 第二个printf就打印2  所以返回值是1
//	// 第一个printf就打印1  所以最后是4321
//	int r = printf("hehe");
//	printf("\n%d ", r);  // 打印的结果就是4 
//
//	return 0;
//}

//int main()
//{
//	printf("%d", printf("%d ", printf("%d ", 43))); // 结果43 3 2
//	// 最里面的printf 打印43[]  所以返回值是3  []是空格  打印了三个字符 所以返回值是3
//	// 第二个printf 打印3[]   所以返回值是2
//	// 第一个printf就打印2
//
//	return 0;
//}

//上面的这种场景下是函数的定义在函数调⽤之前，没啥问题

// 如果我们先调用函数，再定义函数，就需要进行一个函数的声明

// 函数的定义
//bool is_leap_year(int y)
//{
//	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
//		return true;  // 是闰年就返回true
//	else
//		return false;
//}
//int main()
//{
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))
//	{
//		printf("%d是闰年\n", year);
//	}
//	else
//	{
//		printf("%d 不是闰年\n", year);
//	}
//		return 0;
//}

// 如果我把函数定义写到后面去 报错is_leap_year未定义  编译器编译这个代码是从前往后扫描 所以就会出现未定义的情况
// 所以我们要先进行一个函数的声明

//int is_leap_year(int y); // 函数的声明  告诉编译器有这个函数
//// 函数的声明 形参名字可以省略 - y  函数定义是一种特殊的说明
//int main()
//{
//	int year = 0;
//	scanf("%d", &year);
//	if (is_leap_year(year))
//	{
//		printf("%d是闰年\n", year);
//	}
//	else
//	{
//		printf("%d 不是闰年\n", year);
//	}
//	return 0;
//}
//
//int is_leap_year(int y)
//{
//	if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
//		return 1;  // 是闰年就返回1
//	else
//		return 0;
//}

//⼀般在企业中我们写代码时候，代码可能⽐较多，不会将所有的代码都放在⼀个⽂件中；我们往往会
//根据程序的功能，将代码拆分放在多个⽂件中。
//⼀般情况下，函数的声明、类型的声明放在头⽂件（.h）中，函数的实现是放在源⽂件（.c）⽂件中。
// .h文件存放函数的声明  .c文件存放函数的实现
// 我们在创建一个Add.h  Add.c


#include "Add.h"  // 这样包含这个我们自己的头文件，这个函数就能使用了
// 这样也可以声明函数了
//int main()
//{
//	int a = 0;
//	int b = 0;
//	scanf("%d %d", &a, &b);
//	int c = Add(a, b);
//	printf("%d\n", c);
//	return 0;
//}


//static 和 extern 都是C语⾔中的关键字。
//static 是 静态的 的意思，可以⽤来：
//• 修饰局部变量
//• 修饰全局变量
//• 修饰函数
//extern 是⽤来声明外部符号的。

//作⽤域（scope）是程序设计概念，通常来说，⼀段程序代码中所⽤到的名字并不总是有效（可⽤）
//的，⽽限定这个名字的可⽤性的代码范围就是这个名字的作⽤域。
//1. 局部变量的作⽤域是变量所在的局部范围。
//2. 全局变量的作⽤域是整个⼯程（项⽬）。
//⽣命周期指的是变量的创建(申请内存)到变量的销毁(收回内存)之间的⼀个时间段。
//1. 局部变量的⽣命周期是：进⼊作⽤域变量创建，⽣命周期开始，出作⽤域⽣命周期结束。
//2. 全局变量的⽣命周期是：整个程序的⽣命周期。

//int c = 0; // c是全局变量 作用域是整个项目
//int main()
//{
//	int b = 0;  // 这个b的作用域就是main函数中
//	{
//		int a = 100;
//		printf("1 a = %d\n", a);  // 这个大括号是a的作用域
//	}
//	//printf("2 a = %d\n", a);  // 这里打印就会报错，因为a的作用域只在上面的大括号里面使用
//
//	return 0;
//}

// 我在另一个文件里面定义的全局变量，不能在这里使用除非提前声明  extern int d;
//extern int d;  // 声明外部符号，d变量是定义在外部文件所以使用extern的

//int main()
//{
//	{
//		int a = 100;  // 进入大括号创建a 出大括号销毁a 这一段就是他的生命周期
//		printf("%d", a);
//	}
//
//	return 0;
//}

// static 修饰局部变量        

#include <stdio.h>
//void test()
//{
//    int a = 1;  // 创建a 出这个函数a又销毁了 每次都这样循环 所有结果是5个2
//    a++;
//    printf("%d ", a);
//}
//int main()
//{
//    int i = 0;
//    for (i = 0; i < 5; i++)
//    {
//        test();  // 结果是2 2 2 2 2
//    }
//    return 0;
// }
             
//void test()
//{
//    static int a = 1;  // 因为用static这个变量就变成全局变量了
//    // 这样就不会重复创建a 所以a一直在
//    a++;
//    printf("%d ", a);
//}
//int main()
//{
//    int i = 0;
//    for (i = 0; i < 5; i++)
//    {
//        test();  // 结果是2 3 4 5 6
//    }
//    return 0;
//}

//static修饰局部变量改变了变量的⽣命周期，⽣命周期改变的本质是改变了变量的存储类型，本
//来⼀个局部变量是存储在内存的栈区的，但是被 static 修饰后存储到了静态区。存储在静态区的变
//量和全局变量是⼀样的，⽣命周期就和程序的⽣命周期⼀样了，只有程序结束，变量才销毁，内存才
//回收。但是作⽤域不变的。
//使⽤建议：未来⼀个变量出了函数后，我们还想保留值，等下次进⼊函数继续使⽤，就可以使⽤static
//修饰。

// static修饰全局变量

//g_val是在test32.c里面创建的全局变量 变量在使用之前也得声明
//extern int g_val;  // 使用extern声明
//
//int main()
//{
//    printf("%d\n", g_val);
//    return 0;
//}

//extern 是⽤来声明外部符号的，如果⼀个全局的符号在A⽂件中定义的，在B⽂件中想使⽤，就可以使
//⽤ extern 进⾏声明，然后使⽤。
//结论：
//⼀个全局变量被static修饰，使得这个全局变量只能在本源⽂件内使⽤，不能在其他源⽂件内使⽤。
//本质原因是全局变量默认是具有外部链接属性的，在外部的⽂件中想使⽤，只要适当的声明就可以使
//⽤；但是全局变量被 static 修饰之后，外部链接属性就变成了内部链接属性，只能在⾃⼰所在的源
//⽂件内部使⽤了，其他源⽂件，即使声明了，也是⽆法正常使⽤的。
//使⽤建议：如果⼀个全局变量，只想在所在的源⽂件内部使⽤，不想被其他⽂件发现，就可以使⽤
//static修饰。

// static修饰函数

// 声明外部符号
//extern int Add(int x, int y);  
// 函数也是具有外部链接属性的，只要在其他的.c文件中正确的声明，也是可以直接使用的
//int main()
//{
//    int a = 0;
//    int b = 0;
//    scanf("%d %d", &a, &b);
//    int c = Add(a, b);
//    printf("c = %d", c);
//    return 0;
//}

//其实 static 修饰函数和 static
//修饰全局变量是⼀模⼀样的，⼀个函数在整个⼯程都可以使⽤，
//被static修饰后，只能在本⽂件内部使⽤，其他⽂件⽆法正常的链接使⽤了。
//本质是因为函数默认是具有外部链接属性，具有外部链接属性，使得函数在整个⼯程中只要适当的声
//明就可以被使⽤。但是被 static 修饰后变成了内部链接属性，使得函数只能在⾃⼰所在源⽂件内部
//使⽤。
//使⽤建议：⼀个函数只想在所在的源⽂件内部使⽤，不想被其他源⽂件使⽤，就可以使⽤ static 修
//饰。